<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        /* 创建空代理 */
        {
            const target = { id: 1 };
            const handler = {};
            let proxy = new Proxy(target, handler);
            console.log(target.id, proxy.id, target === proxy);
        }

        /* 捕获器get() - 拦截读取操作 */
        {
            const target = { id: 1 };
            const handler1 = {
                // 捕获器在处理程序对象中以方法名为键
                get() {
                    return 'HandlerOverride';
                }
            }
            const proxy1 = new Proxy(target, handler1);
            console.log(target.id, proxy1.id);
            // 捕获器可以接受三个参数: 目标对象、要查询的属性、代理对象
            const handler2 = {
                get(trapTarget, property, receiver) {
                    // return `${trapTarget === target} ${property} ${receiver === proxy2}`;
                    return trapTarget[property] + 1;
                }
            }
            const proxy2 = new Proxy(target, handler2);
            console.log(target.id, proxy2.id);
            // 捕获器解决数组负数索引 
            let createArray = function (...elements) {
                let target = [];
                let handler = {
                    get(target, property, receiver) {
                        let index = Number(property); // 获取下标
                        if (index < 0) {
                            property = String(target.length + index);
                            // 如果下标<0, 在下标基础上加上数组长度
                        }
                        return Reflect.get(...arguments);
                    }
                };
                target.push(...elements);
                return new Proxy(target, handler);
            };
            let arr = createArray('a', 'b', 'c');
            console.log(arr[-1]);
            // 捕获器不变式 - 不能改变不可配置且不可写的数据属性值
            const target3 = {};
            Object.defineProperty(target3, 'id', {
                configurable: false, // 不可配置的
                writable: false, // 不可写的
                value: 3
            });
            const proxy3 = new Proxy(target3, {
                get() {
                    return 2;
                }
            })
            try {
                console.log(proxy3.id);
            } catch (error) {
                console.log('TypeError'); // 尝试修改该属性时抛出TypeError
            }
            // revocable() - 可撤销代理
            const target4 = { id: 4 };
            const handler4 = {
                get() {
                    return 'intercepted';
                }
            };
            const { proxy, revoke } = Proxy.revocable(target4, handler4);
            console.log(target4.id, proxy.id);
            revoke();
            try {
                console.log(proxy.id);
            } catch (error) {
                console.log('TypeError'); // 撤销代理后再调用代理抛出TypeError
            }
        }

        /* 捕获器set() - 拦截赋值操作 */
        // 写一个设置id捕获器, 要求是一个小于100的整数
        {
            const handler1 = {
                set(target, property, value) {
                    if (property == 'id') {
                        if (!Number.isInteger(value)) console.log('ERROR: 请输入一个整数！');
                        if (value > 100) console.log('ERROR: 请输入小于100的整数！');
                    }
                    target[property] = value; // 满足条件的属性赋值成功
                    return true;
                }
            };
            const proxy1 = new Proxy({}, handler1);
            proxy1.id = 130;
            // 写一个私有属性捕获器, 要求下划线开头的属性不能被外部使用
            const invariant = function (property, action) {
                if (property[0] === '_')
                    throw new Error(`Invalid attempt to ${action} private "${property}" property`);
            }
            const handler2 = {
                get(target, property) {
                    invariant(property, 'get');
                    return target[property];
                },
                set(target, property, value) {
                    invariant(property, 'set');
                    target[property] = value;
                    return true;
                }
            }
            const proxy2 = new Proxy({ _prop1: '_' }, handler2);
            // proxy2._prop2 = '+'; // error
            // console.log(proxy2._prop1); // error
        }

        /* 捕获器apply() - 拦截函数调用, call(), apply()操作 */
        {
            const target = (sth) => { console.log('I am target.', sth); };
            const handler = {
                apply(target, thisArg, argsList) {
                    console.log('I am proxy.', argsList[0]);
                    // return Reflect.apply(...arguments);
                }
            }
            const proxy = new Proxy(target, handler);
            target('hi');
            proxy('hello');
            proxy.call(null, 'hahaha');
            proxy.apply(null, ['hohoho']);
        }

        /* 捕获器has() - 拦截HasProperty操作(in运算符) */
        // 隐藏属性不被in发现
        {
            const handler = {
                has(target, property) {
                    if (property[0] === '_') return false;
                    return property in target;
                }
            }
            const proxy = new Proxy({ _prop: '_', prop: 'prop' }, handler);
            console.log('_prop' in proxy, 'prop' in proxy);
        }

        /* 捕获器construct() - 拦截new命令 */
        {
            const handler = {
                construct(target, argsList) {
                    return { value: argsList };
                }
            }
            const proxy = new Proxy(function () { }, handler);
            console.log(new proxy(123, 123).value);
        }

        /* 捕获器deleteProperty() - 拦截delete操作 */
        // 下划线开头的属性不能被删除
        {
            const handler = {
                deleteProperty(target, property) {
                    if (property[0] === '_') {
                        console.log("can't delete");
                        return false;
                    }
                    delete target[property];
                    return true;
                }
            };
            const proxy = new Proxy({ _prop: 1, prop: 2 }, handler);
            delete proxy._prop;
            delete proxy.prop;
            console.log(proxy._prop, proxy.prop);
        }

        /* 捕获器defineProperty() - 拦截Object.defineProperty()操作 */
        // 不能定义下划线开头的属性
        {
            const target = {};
            const handler = {
                defineProperty(target, property, descriptor) {
                    if (property[0] === '_') {
                        console.log("can't define");
                        return false; // 不能添加属性
                    }
                    else return Reflect.defineProperty(...arguments);
                }
            };
            const proxy = new Proxy(target, handler);
            proxy._a = 1;
            proxy.b = 2;
            console.log(proxy._a, proxy.b);
        }

        /* 捕获器getOwnPropertyDescriptor() - 拦截Object.defineOwnPropertyDescriptor()操作 */
        // 下划线开头的属性名返回undefined
        {
            const target = { _prop: '_prop', prop: 'prop' };
            const handler = {
                getOwnPropertyDescriptor(target, property) {
                    if (property[0] === '_') return; // undefined
                    return Reflect.getOwnPropertyDescriptor(...arguments);
                }
            }
            const proxy = new Proxy(target, handler);
            console.log(Object.getOwnPropertyDescriptor(proxy, 'prop'));
            console.log(Object.getOwnPropertyDescriptor(proxy, 'ppap'));
            console.log(Object.getOwnPropertyDescriptor(proxy, '_prop'));
        }

        /* 捕获器getPrototypeOf() - 拦截获取对象原型 */
        {
            const proto = {};
            let Person = function () { };
            let p = new Person();
            console.log(Object.getPrototypeOf(p) === Person.prototype);
            const handler = {
                getPrototypeOf(target) {
                    return proto;
                }
            }
            const proxy = new Proxy(p, handler);
            console.log(Object.getPrototypeOf(proxy) === proto, Object.getPrototypeOf(proxy) === Person.prototype);
        }

        /* 捕获器isExtensible() - 拦截isExtensible()操作 */
        {
            const handler = {
                isExtensible(target) {
                    console.log('called');
                    return true; // 返回值必须与target的isExtensible属性保持一致, 否则就会抛出错误。
                }
            };
            const proxy = new Proxy({}, handler);
            console.log(Object.isExtensible(proxy));
        }

        /* 拦截器ownKeys() - 拦截对象自身属性的读取操作 */
        // 拦截下划线开头的属性名
        {
            const target = { _prop: 1, _ppap: 2, prop: 3, ppap: 4 };
            const handler = {
                ownKeys(target) {
                    return Reflect.ownKeys(target).filter(key => key[0] != '_');
                }
            };
            const proxy = new Proxy(target, handler);
            for (let key of Object.keys(proxy)) {
                console.log(target[key]);
            }
        }

        /* 拦截器preventExtensions() - 拦截Object.preventExtensions()操作 */
        {
            const handler = {
                preventExtensions(target) {
                    Object.preventExtensions(target);
                    return true;
                }
            };
            const proxy = new Proxy({}, handler);
            console.log(Object.preventExtensions(proxy));
        }

        /* 拦截器setPrototypeOf() - 拦截Object.setPrototypeOf()操作 */
        {
            // 拦截修改原型操作
            const handler = {
                setPrototypeOf(target, proto) {
                    // throw new Error('Changing the prototype is forbidden');
                    console.log('Error: Changing the prototype is forbidden');
                    return true;
                }
            };
            const proto = {};
            const target = function () { };
            const proxy = new Proxy(target, handler);
            Object.setPrototypeOf(proxy, proto);
        }

        /* this - 目标对象内部的this关键字指向proxy代理 */
        {
            const target = {
                test() {
                    console.log(this === proxy);
                }
            };
            const proxy = new Proxy(target, {});
            target.test(); // false
            proxy.test(); // true

            const _name = new WeakMap();
            class Person {
                constructor(name) {
                    _name.set(this, name);
                    // this.name = name;
                }
                get name() {
                    return _name.get(this); // 通过this获取name, 使用代理时, this指向proxy
                }
            }
            const jane = new Person('Jane');
            const proxyj = new Proxy(jane, {});
            console.log(jane.name, proxyj.name); // Jane undefined
        }
    </script>
</body>

</html>